package com.yang.strategy.util;

import com.yang.common.constant.CacheConstant;
import com.yang.strategy.client.BeaconCacheClient;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @author yangyongsheng
 * @date 2024/9/7 16:43
 * @description
 */
public class DFAUtil {

    /**
     * 敏感词树
     */
    private static Map dfaMap = new HashMap<>();

    /**
     * 结束标识，是否为敏感词标识
     */
    private static final String IS_END = "isEnd";

    /**
     * 初始化敏感词树
     */
    static {
        // 获取Spring容器中的cacheClient
        BeaconCacheClient cacheClient = (BeaconCacheClient) SpringUtil.getBeanByClass(BeaconCacheClient.class);
        // 获取存储在Redis中的全部敏感词
        Set<String> dirtyWords = cacheClient.smember(CacheConstant.DIRTY_WORD);
        // 调用create，将dfaMap的敏感词树构建
        create(dirtyWords);
    }

    /**
     * 构建敏感词树
     *
     * @param dirtyWords 敏感词Set集合
     */
    public static void create(Set<String> dirtyWords) {
        //1、 声明一个Map作为临时存储
        Map nowMap;//只声明一个引用，通过这个引用来操作dfaMap和word对应的map

        //2、遍历敏感词库
        for (String dirtyWord : dirtyWords) {
            nowMap = dfaMap;//此时的nowMap和dfaMap指向同一个内存空间
            // 对敏感词遍历，依次获取每个字
            for (int i = 0; i < dirtyWord.length(); i++) {
                // 获取敏感词的每个字
                String word = String.valueOf(dirtyWord.charAt(i));//char -> String
                // 判断当前的敏感词树中是否包含当前的字
                Map map = (Map) nowMap.get(word);//敏感词的value的是一个map
                if (map == null) {
                    // 当前敏感词树中没有这个字
                    map = new HashMap();
                    // 将当前的敏感词存入,存入了dfaMap中了，nowMap和dfaMap都执行同一个内存空间
                    nowMap.put(word, map);
                }

                // 操作当前key对应的value的map,nowMap作为临时map
                nowMap = map;//此时nowMap指向了key = word的对应value
                // 如果当前的字，已经有IS_END了，并且值为1，直接不管，
                if (nowMap.containsKey(IS_END) && nowMap.get(IS_END).equals("1")) {
                    continue;//终止for对这个字的处理
                }
                // 如果当前的isEnd没有，或者是0，需要考虑需要改为1
                if (i == dirtyWord.length() - 1) {//已经是敏感词的最后一个字了,需要设置成1
                    // 到最后一个字了。
                    nowMap.put(IS_END, "1");
                } else {//不是敏感词的最后一个字，说明不是敏感词，需要设置为0
                    // isEnd之前就是0，或者压根就没有isEnd
                    nowMap.putIfAbsent(IS_END, "0");//之前map没有，设置为0
                }
            }
        }
    }


    /**
     * 基于敏感词树，对文字进行敏感词获取
     * @param text 需要匹配的内容
     * @return 命中的敏感词集合
     */
    public static Set<String> getDirtyWord(String text) {
        //1、作为返回结果存储敏感词的位置
        HashSet<String> dirtyWords = new HashSet<>();

        // 2、循环遍历文本内容
        for (int i = 0; i < text.length(); i++) {
            // 临时存储索引位置的变量
            // 命中敏感词,该变量是敏感词的下一个索引，未命中，变量就是这次循环的i+1(即下一个字)
            int nextLength = 0;
            // 存储敏感词的长度
            int dirtyLength = 0;

            // 获取最外层key的map(即dfaMap)
            Map nowMap = dfaMap;
            // 外层for i循环是索引向后移，匹配最外层的key
            // 内层for j循环是在外层匹配一个后，继续向内部匹配的key
            for (int j = i; j < text.length(); j++) {
                //获取当前索引位置的字
                String word = String.valueOf(text.charAt(j));
                //先匹配最外层的key
                nowMap= (Map) nowMap.get(word);
                // 判断
                if (nowMap == null){//没匹配上
                    // 没有这个字开头的敏感词
                    break;//跳出内层循环,外层循环 i++ 开始下一个文字匹配
                } else {//匹配上了
                    //敏感词长度，从i开始算，现在的是dirtyLength
                    dirtyLength++;
                    //出口即是，当前的map的isEnd是1，代表结束了。已经找到完整的敏感词
                    if ("1".equals(nowMap.get(IS_END))){//命中
                        nextLength = dirtyLength;
                        break;
                    }
                }
            }

            // 判断是否匹配上了敏感词
            if (nextLength > 0){
                // 匹配上了，添加敏感词到set，同时移动外层索引
                dirtyWords.add(text.substring(i, i + nextLength));//从i位置截取，去长度为nextLength表示敏感词
                // -1的原因是，外层for循环，会对i进行++
                i = i + nextLength - 1;
            }

        }
        //返回
        return dirtyWords;
    }


    //测试
    public static void main(String[] args) {
        //敏感词库
        Set<String> dirtyWords = new HashSet<>();
        dirtyWords.add("三胖");
        dirtyWords.add("山炮");
        dirtyWords.add("三胖啊啊");
        dirtyWords.add("瘦啊");
        create(dirtyWords);
        System.out.println(dfaMap);

        Set<String> dirtyWord = getDirtyWord("你三瘦啊是个大山炮");
        System.out.println(dirtyWord);

    }
}
